结构

  数组是相同类型的元素的一个聚集。一个struct则是(差不多)任意类型元素的一个聚集。例如,

    struct address
    {
        char* name;            // "Jim Dandy"
        long int number;       // 61
        char* street;          // "South St"
        char* town;            // "New Providence"
        char state[2];         // 'N' 'J'
        long zip;              // 7974
    };

这定义了一个新类型,名为address,它由一些你所需要的以便能给某人发送邮件的条目组成。请注意最后的分号。这是C++里很少有的几处在花括号之后还需要写分号的地方,因此人们很容易忘记它。

  类型为address的变量可以像其他变量一样声明,其中的各个成员可以通过.(圆点)运算符访问。例如,

    void f()
    {
        address jd;
        jd.name = "Jim Dandy";
        jd.number = 61;
    }

用于对数组初始化的记法形式也适用于初始化结构类型的变量。例如,

    address jd = {
        "Jim Dandy",
        61, "South St",
        "New Providence", {'N', 'J'}, 7974
    };

当然,一般来说采用构造函数(10.2.3节)更好一些。请注意,jd.state不能用字符串"NJ"去初始化。字符串总以字符'\0'结束,这样,“NJ”实际上就有三个字符---比jd.state的需要多了一个。

  结构对象常常通过指针用 -> 运算符(结构指针间接)访问。例如,

    void print_addr(address* p)
    {
        cout << p->name << '\n'
             << p->number << ' ' << p->street << '\n'
             << p->town << '\n'
             << p->state[0] << p->state[1] << ' ' << p->zip << '\n'; 
    }

当p是指针时,p->m等价于(*p).m。

  结构类型的对象可以被赋值、作为函数参数传递、作为函数的返回值返回。例如,

    address current;

    address set_current(address next)
    {
        address prev = current;
        current = next;
        return prev;
    }

其他可能的运算符,例如比较(==和!=)都没有定义。但是用户可以自己定义这些运算符(第11章)。

  结构类型对象的大小未必是其成员的大小之和,这是因为许多机器要求将确定类型的对象分配在某种与机器的系统结构有关的边界上,或者是在采用适当分配的情况下能更有效地处理这些对象。例如,整数常常被分配在机器字的边界上。在这类机器上,对象被称为具有对齐的性质。对齐会在结构中造成“空洞”。例如,在许多机器上,sizeof(address)是24,而不是人们可能期望的22。你可以通过简单地从大到小排列成员,以取得最小的空间浪费(最大的成员在先)。但是,最好还是按照可读性的要求去排列它们,只在那些必须优化的地方按大小将它们排好顺序。

  类型的名字在出现之后立即就可以使用了,不必等到看到完整的声明之后。例如,

    struct Link
    {
        Link* previous;
        Link* successor;
    };

在完整声明被看到之前,不能去声明这个结构类型的新对象。例如,

    struct No_good
    {
        No_good memeber;        // 错误❌:递归定义
    };

这是一个错误,因为编译将无法确定No_good的大小。要想允许两个(或更多)结构类型互相引用,我们可以先将一个名字声明为结构的名字。例如,

    struct List;            // 后面定义

    struct Link
    {
        Link* pre;
        Link* suc;
        List* member_of;
    };

    struct List
    {
        Link* head;
    };

如果没有List的第一个声明,在Link的声明里使用List就会导致一个语法错误。

  结构类型的名字可以在这个类型的定义之前使用,只要在有关使用中无需知道成员的名字或者结构的大小。例如,

    class S;            // S 是某个类型的名字

    extern S a;
    S f();
    void g(S);
    S* h(S*);

当然,除非类型S已经定义,否则许多其他声明都是无法做的:

    void k(S* p)
    {
        S a;            // 错误❌:S无定义;分配时需要其大小

        f();            // 错误❌:S无定义;返回值需要其大小
        g(a);           // 错误❌:S无定义;传递参数需要其大小
        p->m = 7;       // 错误❌:S无定义;成员名未知

        S* q = h(p);    // 可以✅:指针可以分配和传递
        q->m = 7;       // 错误❌:S无定义;成员名未知
    }

struct是class(第10章)的简单形式。

  由于与C的早期历史接轨的原因,在同一个作用域里,允许一个名字被同时用于一个struct和一个非结构实体。例如,

    struct stat { /* ... */ }
    int stat(char* name, struct stat* buf);

在这种情况下,简单的名字本身(stat)就是那个非结构物的名字,而该结构则必须通过加前缀struct的方式引用。类似地,关键字class、union(C.8.2节)和enum(4.8节)也可用做前缀以消除歧义。当然,最好是不要去重载某个名字,不要造成这种必要性。

🔚